﻿using System;
using System.Collections.Generic;
using Interpreter.Inner;

namespace Interpreter
{
    namespace Inner
    {
        /// <summary>
        /// Debugging Flags
        /// </summary>
        enum MachineFlags
        {
            /// <summary>
            /// Run normally
            /// </summary>
            MACHINE_FLAGS_NONE = 0,
            /// <summary>
            /// Doing step in
            /// </summary>
            MACHINE_FLAGS_STEP_IN = 1,
            /// <summary>
            /// Doing step out
            /// </summary>
            MACHINE_FLAGS_STEP_OUT = 2,
            /// <summary>
            /// Doing step over
            /// </summary>
            MACHINE_FLAGS_STEP_OVER = 3
        }
    }
    /// <summary>
    /// Machine Status
    /// </summary>
    public enum MachineStatus
    {
        /// <summary>
        /// Paused, also the initial state of any machine.
        /// </summary>
        MACHINE_STATUS_PAUSED = 1,
        /// <summary>
        /// Running.
        /// </summary>
        MACHINE_STATUS_RUNNING = 2,
        /// <summary>
        /// Encounted errors, can be CompileException or RuntimeException
        /// </summary>
        MACHINE_STATUS_DEAD = 3,
        /// <summary>
        /// Finished running
        /// </summary>
        MACHINE_STATUS_FINISHED = 4
    }
    

    public class Machine
    {
        #region Properties
        private const int stackSize = 131072;
        private const int codeSize = 131072;
        private const int maxLines = 65536;
        private Variable[] userStack;
        private int[] systemStack;
        private int userStackTop;
        private int systemStackTop;
        private int userStackBase;

        public Instruction[] code;
        private int[] lineCode;
        private int[] codeLine;
        private SortedDictionary<int, string> functionTable;
        private Dictionary<string, Variable> globalMap;
        private List<string> stringMap;
        private Dictionary<string, List<string>> locals;
        private List<string> curFuncLocals;
        private string parser_currentFunction;
        private string errorMessage;
        private int stepOutStackFrame;
        private bool compileComplete;
        private IFunction inSystemFunction;

        private int pc;
        private MachineFlags flags;
        private int codeLength;
        private bool cmpState;
        private bool wantPause;
        private Dictionary<int,bool> breakpointList;

        private System.Threading.EventWaitHandle executeEvent;

        /// <summary>
        /// Bytecode length
        /// </summary>
        public int CodeLength
        {
            get
            {
                return codeLength;
            }
            set
            {
                codeLength = value;
            }
        }
        private MachineStatus status;

        /// <summary>
        /// Current machine status
        /// </summary>
        public MachineStatus Status
        {
            get
            {
                return status;
            }
        }

        /// <summary>
        /// Last error occured
        /// </summary>
        public string LastErrorMessage
        {
            get
            {
                return errorMessage;
            }
        }

        /// <summary>
        /// The next line of source code the machine to be excuted,accessible only if machine is paused
        /// </summary>
        public int CurrentLine
        {
            get
            {
                return symbol_getLineByCode(pc);
            }
        }

        /// <summary>
        /// The function contains CurrentLine
        /// </summary>
        public int CurrentFunction
        {
            get
            {
                return symbol_getFunctionByLine(symbol_getLineByCode(pc));
            }
        }

        /// <summary>
        /// Is the machines has successfully compiled given code
        /// Most of machine operations only valid if CompileComplete == true
        /// </summary>
        public bool CompileComplete
        {
            get
            {
                return compileComplete;
            }
        }
        #endregion
        /// <summary>
        /// Initialize a new machine
        /// </summary>
        public Machine()
        {
            userStack = new Variable[stackSize];
            systemStack = new int[stackSize];
            userStackTop = 0;
            systemStackTop = 0;
            userStackBase = 0;
            code = new Instruction[codeSize];
            globalMap = new Dictionary<string,Variable>();
            stringMap = new List<string>();
            locals = new Dictionary<string, List<string>>();
            wantPause = false;
            flags = MachineFlags.MACHINE_FLAGS_NONE;
            breakpointList = new Dictionary<int, bool>();
            functionTable = new SortedDictionary<int,string>();
            inSystemFunction = null;
            lineCode = new int[maxLines];
            executeEvent = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.ManualReset);
            for (int i = 0; i < maxLines; ++i)
            {
                lineCode[i] = -1;
            }
            status = MachineStatus.MACHINE_STATUS_PAUSED;
            compileComplete = false;
        }
        #region Used only by parser

        /// <summary>
        /// Set current function that's parsing, mainly affect local Variables
        /// </summary>
        /// <param name="func">Current function name</param>
        public void parser_setCurrentFunction(string func)
        {
            if (!locals.ContainsKey(func))
            {
                locals[func] = new List<string>();
            }
            curFuncLocals = locals[func];
            parser_currentFunction = func;
        }

        /// <summary>
        /// Get current function name
        /// </summary>
        /// <returns>current function name</returns>
        public string parser_getCurrentFunction()
        {
            return parser_currentFunction;
        }

        /// <summary>
        /// Check an identifier if it is in the string map (used by globals)
        /// </summary>
        /// <param name="name">Variable name</param>
        /// <param name="add">If not in the string map, if add to the string map</param>
        /// <returns>Index in the string map, -1 if not found</returns>
        public int parser_checkStringMap(string name, bool add)
        {
            if (!stringMap.Contains(name))
            {
                if (!add)
                    return -1;
                stringMap.Add(name);
            }
            return stringMap.IndexOf(name);
        }

        /// <summary>
        /// Add a indetifier as a local Variable
        /// </summary>
        /// <param name="name">Local Variable name</param>
        public void parser_addLocal(string name)
        {
            if (!curFuncLocals.Contains(name))
                curFuncLocals.Add(name);
        }

        /// <summary>
        /// Get local Variable count of current function
        /// </summary>
        /// <returns>Local Variable count of current function</returns>
        public int parser_getLocalCount()
        {
            return curFuncLocals.Count;
        }

        /// <summary>
        /// Set code start position of a line
        /// </summary>
        /// <param name="line">Line number</param>
        /// <param name="codeStartPos">Code start position</param>
        public void parser_setLineCode(int line, int codeStartPos)
        {
            if (lineCode[line] == -1 ||
                lineCode[line] > codeStartPos)
                lineCode[line] = codeStartPos;
        }

        /// <summary>
        /// Register a function to the symbol table
        /// </summary>
        /// <param name="startLine">Start(end?) line of the function</param>
        /// <param name="name">Function name</param>
        public void parser_register_function(int startLine, string name)
        {
            functionTable[startLine] = name;
        }

        /// <summary>
        /// Get the position of first bytecode of a function
        /// </summary>
        /// <param name="line">Line number</param>
        /// <returns>The position of first bytecode of a function</returns>
        private int symbol_getFirstCodeByLine(int line)
        {
            return lineCode[line];
        }

        /// <summary>
        /// Build the symbol table
        /// </summary>
        private void symbol_build()
        {
            codeLine = new int[codeLength];
            SortedDictionary<int, int> s = new SortedDictionary<int, int>();
            for (int i = 0; i < codeLength; ++i)
            {
                codeLine[i] = 1;
            }
            for (int i = 1; i < maxLines; ++i)
            {
                if (lineCode[i] != -1)
                {
                    if (!s.ContainsKey(lineCode[i]))
                        s.Add(lineCode[i], i);
                    else
                        s[lineCode[i]] =  i;
                }
            }
            foreach (KeyValuePair<int, int> kv in s)
            {
                for (int i = kv.Key; i < codeLength; ++i)
                {
                    codeLine[i] = kv.Value;
                }
            }

        }

        /// <summary>
        /// Get the line of given bytecode position
        /// </summary>
        /// <param name="code">Bytecode position</param>
        /// <returns>Line number of given bytecode position</returns>
        private int symbol_getLineByCode(int code)
        {
            return codeLine[code];
        }

        /// <summary>
        /// Get function name of given line
        /// </summary>
        /// <param name="line">Line number</param>
        /// <returns>ID of String in the string map pointing at function name of given line</returns>
        private int symbol_getFunctionByLine(int line)
        {
            string last = "";
            foreach (KeyValuePair<int, string> kvp in functionTable)
            {
                if (kvp.Key > line)
                    return parser_checkStringMap(kvp.Value, true);
                last = kvp.Value;
            }
            return parser_checkStringMap(last, true); 
        }
        #endregion

        #region Variable operations
        /// <summary>
        /// Get the given local ID in current function context
        /// Requires CompileComplete == true 
        /// </summary>
        /// <param name="name">The name of a local Variable</param>
        /// <returns>The ID of given local Variable name. If the local is not exist in current function context, returns -1</returns>
        public int GetLocalIDByName(string name)
        {
            if (compileComplete)
            {
                parser_setCurrentFunction(GetString(symbol_getFunctionByLine(CurrentLine)));
            }
            if (!curFuncLocals.Contains(name))
                    return -1;
            else
                return curFuncLocals.IndexOf(name) + 1;
        }

        /// <summary>
        /// Set the value of a given global varbile.
        /// Requires CompileComplete == true && MachineStatus == MACHINE_STATUS_PAUSED
        /// </summary>
        /// <param name="name">The name of a global Variable</param>
        /// <param name="var">The value to be set</param>
        public void SetGlobal(string name, Variable var)
        {
            globalMap[name] = var;
        }

        /// <summary>
        /// Get the value of a given global varbile.
        /// Requires CompileComplete == true
        /// </summary>
        /// <param name="name">The name of a global Variable</param>
        /// <returns>The value of given global Variable</returns>
        public Variable GetGlobal(string name)
        {
            if (!globalMap.ContainsKey(name))
                throw new RuntimeException("Global Variable " + name + " have not been initialized yet");
            return globalMap[name];
        }

        /// <summary>
        /// Test if a global Variable exists.
        /// Requires CompileComplete == true
        /// </summary>
        /// <param name="name">The name of a global Variable</param>
        /// <returns>true if the global Variable exists, false otherwise</returns>
        public bool IsGlobalExists(string name)
        {
            return globalMap.ContainsKey(name);
        }

        /// <summary>
        /// Set the value of given local Variable
        /// Requires CompileComplete == true && MachineStatus == MACHINE_STATUS_PAUSED
        /// </summary>
        /// <param name="id">ID of local varbile to be set</param>
        /// <param name="var">The value to be set</param>
        public void SetLocalByID(int id, Variable var)
        {
            userStack[userStackBase + id - 1] = var;
        }

        /// <summary>
        /// Get the value of given local Variable
        /// Requires CompileComplete == true
        /// </summary>
        /// <param name="id">ID of a local varbile</param>
        /// <returns>Value of given local Variable</returns>
        public Variable GetLocalByID(int id)
        {
            return userStack[userStackBase + id - 1];
        }
        #endregion
        
        /// <summary>
        /// Get a string in the string map
        /// </summary>
        /// <param name="id">String ID,use CheckStringMap to get ID</param>
        /// <returns>String in the string map</returns>
        public string GetString(int id)
        {
            return stringMap[id];
        }

        #region Breakpoints
        /// <summary>
        /// Add a breakpoint
        /// </summary>
        /// <param name="line">Line number of source code in which the breakpoint is placed</param>
        /// <param name="persistent">If false,the breakpoint will hit only once.</param>
        public void AddBreakpoint(int line, bool persistent)
        {
            while (line < maxLines && symbol_getFirstCodeByLine(line) == -1)
                ++line;
            while (line < maxLines && symbol_getFirstCodeByLine(line) == symbol_getFirstCodeByLine(line + 1))
                line = line + 1;
            if (line == maxLines) return;
            if (breakpointList.ContainsKey(line) && breakpointList[line])
                return; 
            breakpointList[line] = persistent;
            code[symbol_getFirstCodeByLine(line)].opCode |= 0x80;
        }

        /// <summary>
        /// Remove a breakpoint
        /// </summary>
        /// <param name="line">Line number of source code in which the breakpoint is placed</param>
        public void RemoveBreakpoint(int line)
        {
            if (breakpointList.ContainsKey(line))
                breakpointList.Remove(line);
            code[symbol_getFirstCodeByLine(line)].opCode &= 0x7F;
        }

        /// <summary>
        /// Get a breakpoint list
        /// </summary>
        /// <returns>A dictionary key of which contains all breakpoint lines</returns>
        public Dictionary<int, bool> GetBreakpointList()
        {
            return breakpointList;
        }

        #endregion

        #region Debugging & Control
        /// <summary>
        /// Pause the machine
        /// Requires CompileComplete == true && MachineStatus == MACHINE_STATUS_RUNNING
        /// </summary>
        public void Pause()
        {
            wantPause = true;
        }
        /// <summary>
        /// Get current call stack.
        /// Requires CompileComplete == true && MachineStatus == MACHINE_STATUS_PAUSED
        /// </summary>
        /// <returns>A call stack list.</returns>
        public List<StackInfo> GetCallStack()
        {
            List<StackInfo> L = new List<StackInfo>();
            StackInfo s;
            int maxStackIndex = stackSize - 1;
            int t = systemStackTop > maxStackIndex ?
                maxStackIndex - maxStackIndex % 3 : systemStackTop;
            int oldPC = 0;
            int usb = userStackBase;
            s.line = symbol_getLineByCode(pc);
            s.funcName = GetString(symbol_getFunctionByLine(s.line));
            if (inSystemFunction != null) s.funcName = "<System Function>:" + inSystemFunction.GetType().ToString();
            while (t > 0)
            {
                s.parmList = new List<Variable>();
                for (int i = 0; i < systemStack[t - 1]; ++i)
                {
                    s.parmList.Add(userStack[usb + i]);
                }
                L.Add(s);
                usb = systemStack[t - 2];
                oldPC = systemStack[t - 3];
                s.line = symbol_getLineByCode(oldPC);
                s.funcName = GetString(symbol_getFunctionByLine(s.line));
                t -= 3;
            }
            s.parmList = new List<Variable>();
            L.Add(s);

            return L;
        }

        /// <summary>
        /// Start Step In.
        /// Requires CompileComplete == true && MachineStatus == MACHINE_STATUS_PAUSED
        /// </summary>
        public void StepIn()
        {
            stepOutStackFrame = systemStackTop;
            flags = MachineFlags.MACHINE_FLAGS_STEP_IN;
            _execute();
        }

        /// <summary>
        /// Start Step Over.
        /// Requires CompileComplete == true && MachineStatus == MACHINE_STATUS_PAUSED
        /// </summary>
        public void StepOver()
        {
            stepOutStackFrame = systemStackTop;
            if (pc == 0)
                flags = MachineFlags.MACHINE_FLAGS_STEP_IN;
            else
                flags = MachineFlags.MACHINE_FLAGS_STEP_OVER;
            _execute();
        }

        /// <summary>
        /// Start Step Out.
        /// Requires CompileComplete == true && MachineStatus == MACHINE_STATUS_PAUSED
        /// </summary>
        public void StepOut()
        {
            stepOutStackFrame = systemStackTop;
            flags = MachineFlags.MACHINE_FLAGS_STEP_OUT;
            _execute();
        }

        /// <summary>
        /// Start Executing the code.
        /// Requires CompileComplete == true && MachineStatus == MACHINE_STATUS_PAUSED
        /// </summary>
        public void Execute()
        {
            flags = MachineFlags.MACHINE_FLAGS_NONE;
            _execute();
        }

        private void _execute()
        {
            System.Threading.Thread t = new System.Threading.Thread(executeThread);
            executeEvent.Reset();
            t.Start();
            //Wait the execution thread to run
            executeEvent.WaitOne(System.Threading.Timeout.Infinite);
        }

        private void executeThread()
        {
            int currentLine = CurrentLine;
            wantPause = false;
            try
            {
                status = MachineStatus.MACHINE_STATUS_RUNNING;
                executeEvent.Set();
                executeInner();
                status = MachineStatus.MACHINE_STATUS_FINISHED;
            }
            catch (BreakpointException)
            {
                status = MachineStatus.MACHINE_STATUS_PAUSED;
                int line = symbol_getLineByCode(pc);
                if (breakpointList.ContainsKey(line) && breakpointList[line] == false)
                    RemoveBreakpoint(line);
            }
            catch (RuntimeException e)
            {
                status = MachineStatus.MACHINE_STATUS_DEAD;
                errorMessage = e.Description;
            }
            catch (Exception e)
            {
                status = MachineStatus.MACHINE_STATUS_DEAD;
                errorMessage = e.Message;
            }
        }
        #endregion

        #region Compiling
        /// <summary>
        /// Set start PC to first bytecode of main function
        /// </summary>
        private void setPCMain()
        {
            if (!IsGlobalExists("main"))
                throw new CompileException(1, "Global function \"main\" not found");
            Variable t = GetGlobal("main");
            if (t.type != VarType.TYPE_FUNCTION)
                throw new CompileException(1, "Function main type mismatch");
            //Jump to PC
            code[0].opCode = OpCode.JUMPA;
            code[0].pointer = GetGlobal("main").pointer;
        }

        /// <summary>
        /// Compile a Logoscript program
        /// Requires CompileComplete == false && MachineStatus == MACHINE_STATUS_PAUSED
        /// </summary>
        /// <param name="source"></param>
        public void Compile(IStream source)
        {
            Lexer l = new Lexer(source);
            Parser p = new Parser(this, l);
            Library.OpenLibrary(this);
            p.parse();
            symbol_build();
            setPCMain();
            compileComplete = true;
        }
        #endregion

        #region Execution
        /// <summary>
        /// Main loop of the virtual machine
        /// </summary>
        private void executeInner()
        {
            Variable t;
            Variable __parent = new Variable("__parent");
            int n; 
            double num;
            bool finalTable;
            inSystemFunction = null;
            int lastLine = symbol_getLineByCode(pc);
            int currFunc = CurrentFunction;
            short opCode = Convert.ToInt16(code[pc].opCode & 0x7F);
            while (pc < codeLength)
	        {
		        switch(opCode)
		        {
                    //PUSHNUM: Push a number to the user stack
                    //ins.num: The number to be pushed
		            case OpCode.PUSHNUM:
                        userStack[userStackTop].num = code[pc].num;
                        userStack[userStackTop].type = VarType.TYPE_NUMBER;
                        userStackTop ++;
			            break;
                    //POP: Pop Variables from the user stack
                    //ins.pointer : The number of Variables to be poped
		            case OpCode.POP:
			            userStackTop -= code[pc].pointer;
			            break;
                    //GETLOCAL: Get the value of a local Variable and push it to user stack
                    //ins.pointer: local ID
		            case OpCode.GETLOCAL:
			            userStack[userStackTop] = userStack[userStackBase + code[pc].pointer - 1];
                        userStackTop ++;
			            break;
                    //SETLOCAL: Set the value of a local Variable to be the value of user stack top
                    //ins.pointer: local ID
		            case OpCode.SETLOCAL:
			            userStack[userStackBase + code[pc].pointer - 1] = userStack[userStackTop - 1];
			            break;
                    //GETGLOBAL: Get the value of a global Variable and push it to user stack
                    //ins.pointer: Global ID(In the string map)
		            case OpCode.GETGLOBAL:
                        userStack[userStackTop] = GetGlobal(stringMap[code[pc].pointer]);
                        userStackTop ++;
			            break;
                    //SETGLOBAL: Set the value of a global Variable to be the value of user stack top
                    //ins.pointer: Global ID(In the string map)
		            case OpCode.SETGLOBAL:
                        SetGlobal(stringMap[code[pc].pointer],userStack[userStackTop - 1]);
			            break;
                    //CALL: Call a rountine, pop the calling function from stack
                    // Preserve old PC and stack base to system stack
                    // And transfer control to the calling function
                    //ins.pointer: Number of params of calling function
                    //User Stack :
                    //////////////////////////////////////////////////////////////////////////
                    //  [StackTop]              
                    //  Calling address             [StackTop]
                    //  Param N                     Param N
                    //  Param N-1           ->      Param N-1
                    //  .....                       .....
                    //  Param 1                     Param 1
                    //  Another element N           [Stack Bottom] Another element N
                    //  Another element N-1         Another element N-1
                    //   ...                        ...
                    //  [Stack Bottom]              [OLD stack bottom]
                    //////////////////////////////////////////////////////////////////////////
                    //System Stack
                    //  [StackTop]
                    //  Param Num
                    //  Old user stack bottom
                    //  Old PC
                    //  [Stack Bottom]
		            case OpCode.CALL:
                        systemStack[systemStackTop] = pc;
                        systemStackTop ++;
                        systemStack[systemStackTop] = userStackBase;
                        systemStackTop ++;
                        systemStack[systemStackTop] = code[pc].pointer;
                        systemStackTop++;
			            t = userStack[--userStackTop]; // new_address
			            userStackBase = userStackTop - code[pc].pointer;
			            if (t.type == VarType.TYPE_FUNCTION)
			            {
				            pc = t.pointer - 1;
				            break;
                        }
                        else if (t.type == VarType.TYPE_C_FUNCTION)
                        {
                            //C function
                            inSystemFunction = t.func;
                            userStack[userStackTop] = t.func.invoke(this, code[pc].pointer);
                            userStackTop++;
                            inSystemFunction = null;
                            goto retInstruction;
                            // Fall through to RET 
                        }
                        else
                        {
                            userStackBase = systemStack[systemStackTop - 2];
                            systemStackTop -= 3;
                            throw new RuntimeException("Unable to call " + t.num.ToString());
                        }
                    //RET: Return from a function call
                    //Pop 3 params from the system stack, restore PC and User stack bottom
                    //And push the return value to the user stack
                    //User Stack:
                    ///////////////////////////////////////////////////////////////////////////
                    //  [StackTop]              
                    //  Return Value
                    //  ...
                    //  Local N+1                   
                    //  Param N                     
                    //  Param N-1                       ->      
                    //  .....                                   [StackTop]
                    //  Param 1                                 Return value
                    //  [Stack Bottom]Another element N         Another element N
                    //  Another element N-1                     Another element N-1
                    //   ...                                    ...
                    //  [OLD Stack Bottom]                      [Stack bottom]
                    ////////////////////////////////////////////////////////////////////////////
		            case OpCode.RET:
                    retInstruction:
                        if (systemStackTop == 0)
                            return;
			            t = userStack[userStackTop - 1]; //retval
			            userStackTop = userStackBase;
                        systemStackTop--; //pop num param
                        userStackBase = systemStack[--systemStackTop];
                        pc = systemStack[--systemStackTop];
                        opCode = code[pc].opCode;

                        userStack[userStackTop++] = t;
                        if (systemStackTop < stepOutStackFrame && 
                            (flags == MachineFlags.MACHINE_FLAGS_STEP_OUT ||
                            flags == MachineFlags.MACHINE_FLAGS_STEP_OVER || 
                            flags == MachineFlags.MACHINE_FLAGS_STEP_IN)
                            )
                        {
                            ++pc;
                            throw new BreakpointException();
                        }
			            break;
                    //CMP : Compare two values at the top of user stack, pop them, push the compare value onto the user stack
                    //ins.pointer : Combinations of Compare Mode
                    //First number : the top but one value
                    //Second number : the top value
                    //COMPARE_EQUAL: First number == Second number
                    //COMPARE_LESS: First number < Second number
                    //COMPARE_GREATER: First number > Second number
		            case OpCode.CMP:
                        if (userStack[userStackTop - 1].type != VarType.TYPE_NUMBER || userStack[userStackTop - 2].type != VarType.TYPE_NUMBER)
                            throw new RuntimeException("Unable to comapre " + VarType.getTypeString(userStack[userStackTop - 1].type) + " and " 
                                + VarType.getTypeString(userStack[userStackTop - 2].type) );
			            num = userStack[userStackTop - 2].num - userStack[userStackTop - 1].num;
			            if ( Math.Abs(num) < 1e-10 )
			            {
				            cmpState = (code[pc].pointer & CompareMode.COMPARE_EQUAL) != 0;
			            } else
			            {
				            if (num < 0)
                                cmpState = (code[pc].pointer & CompareMode.COMPARE_LESS) != 0;
				            else
                                cmpState = (code[pc].pointer & CompareMode.COMPARE_GREATER) != 0;
			            }
                        --userStackTop;
                        if (cmpState)
			            {
				            userStack[userStackTop - 1].num = 1.0;
			            }
			            else
			            {
                            userStack[userStackTop - 1].num = 0.0;
			            }
			            break;
                    //JUMP: Conditional Jump, pop the top value from the user stack, and jump if the value is non-zero
                    //ins.pointer: PC-relative offset
		            case OpCode.JUMP:
                        if (userStack[userStackTop - 1].type == VarType.TYPE_NUMBER)
                            cmpState = !(Math.Abs(userStack[--userStackTop].num) < 1e-10);
                        else
                            cmpState = true;
                        if (!cmpState)
                            break;
                        else
                        {
                            pc += (code[pc].pointer - 1);
                            break;
                        }
                    //JUMPA: Jump always.
                    //ins.pointer: PC-relative offset
		            case OpCode.JUMPA:
                        pc += (code[pc].pointer - 1);
			            break;
                    //ADJLOCAL: Adjust local count
                    //ins.pointer: Local count
		            case OpCode.ADJLOCAL:
			            n = userStackTop - userStackBase;
			            while(n > code[pc].pointer)
			            {
                            --userStackTop;
				            --n;
			            }
			            while(n < code[pc].pointer)
			            {
                            userStack[userStackTop] = new Variable(0.0);
                            ++userStackTop;
				            ++n;
			            }
			            break;
                    //NOT: Invert the value on top of the user stack
                    //non-zero->zero zero->one
		            case OpCode.NOT:
                        if (userStack[userStackTop - 1].type == VarType.TYPE_NUMBER && (Math.Abs(userStack[userStackTop - 1].num) < 1e-10))
                            userStack[userStackTop - 1].num = 1;
                        else
                        {
                            userStack[userStackTop - 1].type = VarType.TYPE_NUMBER;
                            userStack[userStackTop - 1].num = 0;
                        }
			            break;
                    //ARITH: Perfrom arithmatic operation of two values on the top of user stack and push the result onto the user stack
                    //ins.pointer: arith mode
                    //First number : the top but one value
                    //Second number : the top value
                    //ARITH_ADD: Result = First number + Second number
                    //ARITH_SUB: Result = First number - Second number
                    //ARITH_MUL: Result = First number * Second number
                    //ARITH_DIV: Result = First number / Second number
		            case OpCode.ARITH:
                        if (code[pc].pointer == OpCode.ARITH_ADD && userStack[userStackTop - 1].type == VarType.TYPE_STRING &&
                            userStack[userStackTop - 2].type == VarType.TYPE_STRING)
                        {
                            userStack[userStackTop - 2].str = userStack[userStackTop - 2].str + userStack[userStackTop - 1].str;
                            userStackTop--;
                            break;
                        }
                        if (userStack[userStackTop - 1].type != VarType.TYPE_NUMBER || userStack[userStackTop - 2].type != VarType.TYPE_NUMBER)
                            throw new RuntimeException("Unable to perform arithmatic on " + VarType.getTypeString(userStack[userStackTop - 1].type) + " and "
                                + VarType.getTypeString(userStack[userStackTop - 2].type) );
			            switch (code[pc].pointer)
			            {
                            case OpCode.ARITH_ADD:
                                num = userStack[userStackTop - 2].num + userStack[userStackTop - 1].num;
				                break;
                            case OpCode.ARITH_SUB:
                                num = userStack[userStackTop - 2].num - userStack[userStackTop - 1].num;
				                break;
                            case OpCode.ARITH_MUL:
                                num = userStack[userStackTop - 2].num * userStack[userStackTop - 1].num;
				                break;
                            case OpCode.ARITH_DIV:
                                num = userStack[userStackTop - 2].num / userStack[userStackTop - 1].num;
				                break;
			            default:
                                throw new RuntimeException("Unknown arith type " + code[pc].pointer.ToString());
			            }
                        userStackTop--;
                        userStack[userStackTop - 1].num = num;
			            break;
                    //PUSHSTRING: Push a string to the user stack
                    //ins.pointer: The string to be pushed (ID in the string map)
                    case OpCode.PUSHSTRING:
                        userStack[userStackTop].str = GetString(code[pc].pointer);
                        userStack[userStackTop].type = VarType.TYPE_STRING;
                        userStackTop ++;
			            break;
                    //TABLEOP: Perform table operations
                    //ins.pointer: specify the operition
                    case OpCode.TABLEOP:
                        switch (code[pc].pointer)
                        {
                            //TABLE_NEW: Push a new table onto the stack
                            case OpCode.TABLE_NEW:
                                userStack[userStackTop].table = new Dictionary<Variable,Variable>(VarCompare.GetInstance());
                                userStack[userStackTop].type = VarType.TYPE_TABLE;
                                userStackTop ++;
                                break;
                            //TABLE_GETITEM: Get the value of a key in the table, and push it onto the stack, also pop table and key
                            //User stack:
                            ///////////////////////////////////////////////////////////////////////////////
                            //  [Stack Top]
                            //  Key                           [Stack Top]
                            //  Table                  ->     Value
                            //  .....                         .......
                            ///////////////////////////////////////////////////////////////////////////////
                            case OpCode.TABLE_GETITEM:
                                Dictionary<Variable, Variable> table;
                                int i = 0;
                                if (userStack[userStackTop - 2].type != VarType.TYPE_TABLE)
                                {
                                    //If the "table" is a string, lookup the internal String library
                                    if (userStack[userStackTop - 2].type == VarType.TYPE_STRING)
                                    {
                                        if (IsGlobalExists("String") && GetGlobal("String").type == VarType.TYPE_TABLE)
                                            table = GetGlobal("String").table;
                                        else
                                            throw new RuntimeException("Not a table");
                                    }
                                    else
                                        throw new RuntimeException("Not a table");
                                }
                                else
                                    table = userStack[userStackTop - 2].table;

                                finalTable = false;
                                //If key not exist in the table, look up __parent recursively
                                while (!table.ContainsKey(userStack[userStackTop - 1]))
                                {
                                    if (finalTable)
                                        throw new RuntimeException("Key " + userStack[userStackTop - 1].ToString(this) + " do not exist");
                                    if (table.ContainsKey(__parent) && table[__parent].type == VarType.TYPE_TABLE)
                                    {
                                        table = table[__parent].table;
                                        ++i;
                                        if (i > 256)
                                            throw new RuntimeException("Max levels of __parent exceeded");
                                    }
                                    //__parent not exist, lookup internal Table library
                                    else if (IsGlobalExists("Table") && GetGlobal("Table").type == VarType.TYPE_TABLE)
                                    {
                                        table = GetGlobal("Table").table;
                                        finalTable = true;
                                    }
                                    else finalTable = true;
                                }
                                userStack[userStackTop - 2] = table[userStack[userStackTop - 1]];
                                userStackTop--;
                                break;
                            //TABLE_SETITEM: Set the value of a key in the table, pop table and key
                            //User stack:
                            ///////////////////////////////////////////////////////////////////////////////
                            //  [Stack Top]
                            //  Key
                            //  Table                         [Stack Top]
                            //  Value                  ->     Value
                            //  .....                         .......
                            ///////////////////////////////////////////////////////////////////////////////
                            case OpCode.TABLE_SETITEM:
                                if (userStack[userStackTop - 2].type != VarType.TYPE_TABLE)
                                    throw new RuntimeException("Not a table");
                                userStack[userStackTop - 2].table[userStack[userStackTop - 1]] = userStack[userStackTop - 3];
                                userStackTop -= 2;
                                break;
                            //TABLE_SETITEM_INIT: Same as TABLE_SETITEM, but not pop the table
                            case OpCode.TABLE_SETITEM_INIT:
                                if (userStack[userStackTop - 3].type != VarType.TYPE_TABLE)
                                    throw new RuntimeException("Not a table");
                                userStack[userStackTop - 3].table[userStack[userStackTop - 1]] = userStack[userStackTop - 2];
                                userStackTop -= 1;
                                break;
                            default:
                                throw new RuntimeException("Unknown table op");
                        }
                        break;
                    // BREAK and CONTINUE not processed by the CodeGenerator, must be out of while/for loop
		            case OpCode.PSEUDO_BREAK:
		            case OpCode.PSEUDO_CONTINUE:
                        throw new RuntimeException("Unexpected break or continue outside while/for loop");
		            default:
                        //Encounter a breakpoint
                        if ((code[pc].opCode & 0x80) != 0)
                            throw new BreakpointException();
                        else
                            throw new RuntimeException("Unknown instruction");
		        }
		        ++pc;
                opCode = code[pc].opCode;
                if (wantPause)
                    throw new BreakpointException();
                if (flags == MachineFlags.MACHINE_FLAGS_STEP_IN)
                {
                    //Step in means that executed to a different line
                    if (lastLine != symbol_getLineByCode(pc))
                        throw new BreakpointException();
                }
                if (flags == MachineFlags.MACHINE_FLAGS_STEP_OVER)
                {
                    //Step over means that executed to a different line in current function / upper function
                    if (systemStackTop == stepOutStackFrame &&  CurrentFunction == currFunc && 
                        lastLine != symbol_getLineByCode(pc))
                        throw new BreakpointException();
                }
	        }
	        return;
        }
        #endregion
    }
}
